package qj.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import qj.tool.http.HtmlUtil;
import qj.util.funct.F1;
import qj.util.funct.P1;
import qj.util.math.Range;

public class MarkUp implements Cloneable, Comparable<MarkUp> {
    public static MarkUp DEFAULT = new MarkUp();

    public MarkUp(Range range) {
        this.range = range;
    }

    public MarkUp() {

    }

    Range range;
    String bgColor;
    String color;
    boolean underline;
    boolean allowDetailedSelect = false;
    public static F1<MarkUp, Range> allowSelect = new F1<MarkUp, Range>() {public Range e(MarkUp markUp) {
        return markUp.allowDetailedSelect ? markUp.range : null;
    }};
    public static F1<MarkUp, Range> ranges = new F1<MarkUp, Range>() {public Range e(MarkUp markUp) {
        return markUp.range;
    }};

    public static String toString(Collection<MarkUp> markUps) {
        StringBuilder sb = new StringBuilder();
        sb.append(MathUtil.toString(Cols.yield(markUps, new F1<MarkUp, Range>() {
            public Range e(MarkUp obj) {
                if (obj.allowDetailedSelect) {
                    return obj.range;
                }
                return null;
            }
        })));
        sb.append("\n");
        sb.append(MathUtil.toString(Cols.yield(markUps, new F1<MarkUp, Range>() {
            public Range e(MarkUp obj) {
                if (obj.bgColor != null) {
                    return obj.range;
                }
                return null;
            }
        })));
        return sb.toString();
    }
    public static P1<MarkUp> color(final String color) {
        return new P1<MarkUp>() {
            public void e(MarkUp obj) {
                obj.color = color;
            }
        };
    }

    public static P1<MarkUp> underline(final boolean underline) {
        return new P1<MarkUp>() {
            public void e(MarkUp obj) {
                obj.underline = underline;
            }
        };
    }
    public static P1<MarkUp> allowDetailedSelect(final boolean allowDetailedSelect) {
        return new P1<MarkUp>() {
            public void e(MarkUp obj) {
                obj.allowDetailedSelect = allowDetailedSelect;
            }
        };
    }

    public Map<String, String> htmlStyle() {
        HashMap<String, String> ret = new HashMap<String, String>(3);
        if (bgColor != null) {
            ret.put("background-color", bgColor);
        }
        if (color != null) {
            ret.put("color", color);
        }

        if (underline) {
            ret.put("text-decoration", "underline");
        }
        return ret;
    }

    public String toHtml(String str) {
        Map<String, String> styles = htmlStyle();
        if (styles.isEmpty()) {
            return HtmlUtil.nomalizedHtml(str);
        } else {
            return "<span style=\"" + Cols.join(styles, "; ") + "\">" + HtmlUtil.nomalizedHtml(str) + "</span>";
        }
    }

    public static String markUp(String line, List<MarkUp> markUps) {
        if (Cols.isNotEmpty(markUps)) {
            StringBuilder sb = new StringBuilder();
            int index = 0;
            for (MarkUp markUp : markUps) {
                if (markUp.range.getFrom() > index) {
                    String str = line.substring(index, markUp.range.getFrom());
                    sb.append(DEFAULT.toHtml( HtmlUtil.nomalizedHtml(str) ));
                }
                sb.append(markUp.toHtml(line.substring(markUp.range.getFrom(), markUp.range.getTo())));
                index = markUp.range.getTo();
            }
            if (index < line.length()) {
                sb.append(DEFAULT.toHtml(line.substring(index)));
            }
            return sb.toString();
        } else {
            return line;
        }
    }

    public static void clearBgColor(List<MarkUp> markUps) {
        alterAll(markUps, bgColor(null));
    }

    public static void alterAll(List<MarkUp> markUps, P1<MarkUp> alterF) {
        for (Iterator<MarkUp> iterator = markUps.iterator(); iterator.hasNext();) {
            MarkUp markUp = iterator.next();
            alterF.e(markUp);
            if (DEFAULT.sameMarkup(markUp)) {
                iterator.remove();
            }
        }
        join(markUps);
    }

    public static void setBgColor(List<Range> ranges, List<MarkUp> markUps, final String bgColor) {
        alter(ranges, markUps, bgColor(bgColor));
    }

    public static P1<MarkUp> bgColor(final String bgColor) {
        return new P1<MarkUp>() {
            public void e(MarkUp obj) {
                obj.bgColor = bgColor;
            }
        };
    }

    public static void alter(List<Range> ranges, List<MarkUp> markUps, P1<MarkUp> alterF) {
        TreeSet<MarkUp> ret = new TreeSet<MarkUp>();
        // In old markups
        for (MarkUp markUp : markUps) {
            List<Range> ands = MathUtil.and(markUp.range, ranges);
            if (ands.isEmpty()) {
                ret.add(markUp);
            } else {
                Collection<MarkUp> andClones = markUp.clone(ands);
                for (MarkUp andClone : andClones) {
                    alterF.e(andClone);
                }
                ret.addAll(andClones);
                ret.addAll( markUp.clone(MathUtil.andNot(markUp.range, ranges)) );
            }
        }

        for (Range range : MathUtil.andNot(ranges, Cols.yield(markUps, MarkUp.ranges))) {
            MarkUp markUp = new MarkUp();
            markUp.range = range;
            alterF.e(markUp);
            ret.add(markUp);
        }

        join(ret);

        markUps.clear();
        markUps.addAll(ret);
    }

    private static void join(Collection<MarkUp> markUps) {
        MarkUp last = null;
        for (Iterator<MarkUp> iterator = markUps.iterator(); iterator.hasNext();) {
            MarkUp markUp = iterator.next();
            if (last!= null && last.sameMarkup(markUp) &&
                    last.range.getTo().equals(markUp.range.getFrom())) {
                // Join
                last.range = new Range(last.range.getFrom(), markUp.range.getTo());
                iterator.remove();
            } else {
                last = markUp;
            }
        }
    }

    private MarkUp clone(Range range) {
        MarkUp clone = this.clone();
        clone.range = range;
        return clone;
    }
    private Collection<MarkUp> clone(List<Range> ranges) {
        ArrayList<MarkUp> ret = new ArrayList<MarkUp>();
        for (Range range : ranges) {
            MarkUp clone = this.clone(range);
            ret.add(clone);
        }
        return ret;
    }

    @Override
    protected MarkUp clone() {
        try {
            return (MarkUp) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    public boolean sameMarkup(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        MarkUp markUp = (MarkUp) o;

        if (allowDetailedSelect != markUp.allowDetailedSelect) return false;
        if (underline != markUp.underline) return false;
        if (bgColor != null ? !bgColor.equals(markUp.bgColor) : markUp.bgColor != null) return false;
        if (color != null ? !color.equals(markUp.color) : markUp.color != null) return false;
//        if (!range.equals(markUp.range)) return false;
        return true;
    }

    public int compareTo(MarkUp o) {
        return this.range.getFrom().compareTo(o.range.getFrom());
    }

    public static void underline(List<Range> ranges, List<MarkUp> markUps) {
        alter(ranges, markUps, underline(true));
    }
    public static void clearUnderline(List<MarkUp> markUps) {
        alterAll( markUps, underline(false) );
    }

}